﻿using System.Collections.Generic;
using OverStore.Exceptions;

namespace OverStore.Runtime.ObjectRepository.Depot
{
    /// <summary>
    /// Manages relationships between instances and its identifiers, and manages instances creation and assigning identifiers
    /// for instances.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the instance identifier.</typeparam>
    /// <remarks>
    /// <para>
    /// Depot manages relationships between instances and corresponding identifiers. Also it uses helper sub-component
    /// <see cref="OverStore.Runtime.ObjectRepository.Depot.IdentifierGenerator.IIdentifierGenerator{TInstance, TIdentifier}"/> for
    /// generate identifiers for objects intended to be added in data storage.
    /// </para>
    /// <para>
    /// Depot implementation must support identity mapping: maintain only one instance for one identifier.
    /// </para>
    /// </remarks>
    public interface IDepot<TInstance, TIdentifier> : IRuntimeComponent
        where TInstance : class
    {
        /// <summary>
        /// Gets the stored copy of identifier for the instance. 
        /// Stored identifier is the value of instance's depot identifier copied to instance's data.
        /// If there is no identifier's copy in instance data, method must returns <c>default(TIdentifier)</c>.
        /// </summary>
        TIdentifier GetStoredIdentifier(TInstance instance);

        /// <summary>
        /// Sets the stored identifier to the instance.
        /// </summary>
        void SetStoredIdentifier(TInstance instance, TIdentifier oldIdentifier);

        /// <summary>
        ///     <para> Return identifier of specified instance.</para>
        ///     <para>If instance does not registered in depot, the instance will be placed in depot, and new identifier will be generated and assigned with instance.</para>
        ///     <para>If instance has new identifier pending but not applied, the new pending identifier will be returned. 
        ///     It is required for enforce foreign key constrains in database: 
        ///     if instance is saved and got new identifier generated, other instances must be able to reference it 
        ///     by that generated identifier.</para>
        /// </summary>
        /// <param name="instance">The instance to get identifier from.</param>
        /// <returns>The instance identifier</returns>
        /// <exception cref="PersistingRuntimeException">Error occured while get object identifier.</exception>
        TIdentifier GetObjectIdentifier(TInstance instance);

        /// <summary>
        /// Puts new identifier for the instance registered in session.
        /// After update the instance will no longer accessible by old identifier.
        /// If instance does not registered in session, it will be placed in session cache with specified identifier.
        /// Putting identifier for instance already has new pending identifier will overwrite old pending identifier.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="newIdentifier">The new identifier.</param>
        /// <exception cref="PersistingRuntimeException">Error occured while update object identifier.</exception>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same key already registered.</exception>
        void PutNewIdentifier(TInstance instance, TIdentifier newIdentifier);

        /// <summary>
        /// Replaces the identifier of the instance registered in session with the 
        /// new identifier that was put before by calling PutNewIdentifier method.
        /// After accepting new identifier instance become available to get by new identifier, 
        /// and old identifier discards and becomes invalid.
        /// If there is pending new identifier for the instance, method updates instance's stored identifier copy.
        /// </summary>
        /// <param name="instance">The instance.</param>
        void AcceptNewIdentifier(TInstance instance);

        /// <summary>
        /// Removes identifier for the instance that was put by calling PutNewIdentifier method.
        /// This method has effect only before AcceptNewIdentifier method call.
        /// </summary>
        void RejectNewIdentifier(TInstance instance);

        /// <summary>
        /// Put instance in depot if instance was not registered in depot previously. 
        /// If instance is already registered in depot, the method returns false.
        /// </summary>
        /// <param name="instance">The instance to put in depot.</param>
        /// <returns><c>true</c> if instance successfuly put into depot, false if instance with such identifier already exists.</returns>
        /// <exception cref="PersistingRuntimeException">Error is occured while put instance.</exception>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same key already registered.</exception>
        bool TryPutInstance(TInstance instance);

        /// <summary>
        /// Get object from depot using identifier. If object
        /// with such identifier not exist in depot, the new instance
        /// creates and puts into depot.
        /// The creates out parameter is set to true if object was created,
        /// and set to false, if object already exist.
        /// </summary>
        /// <param name="identifier">The identifier to get or create object with it.</param>
        /// <param name="created">if set to <c>true</c> it means object was created; otherwise method returns instance already registered in depot.</param>
        /// <exception cref="PersistingRuntimeException">Error is occured while get or create object.</exception>
        TInstance GetOrCreate(TIdentifier identifier, out bool created);

        /// <summary>
        /// Remove instance from depot and removes pending new identifier for the instance.
        /// </summary>
        /// <param name="instance">The instance to remove from depot.</param>
        void RemoveObject(TInstance instance);

        /// <summary>
        /// Get all object registered in depot.
        /// </summary>
        /// <returns>Iterator throught set of registered objects.</returns>
        IEnumerable<TInstance> RegisteredObjects();

        /// <summary>
        /// Determines whether the specified instance is registered in depot.
        /// </summary>
        /// <param name="instance">The instance to determine whether is it registered.</param>
        /// <returns>
        ///     <c>true</c> if the instance registered in depot; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="instance"/> is null reference.</exception>
        bool IsInstanceRegistered(TInstance instance);
    }
}